// 文件基本操作

#include <windows.h>
#include <string.h>
#include <errno.h>
#include <malloc.h>
#include <conio.h>
#include "InputFormat.h"
#include "FileManage.h"

/**
 * 指定路径，打开文件
 *
 * 接收一个文件路径参数
 * 尝试以二进制读写模式("rb+")打开文件
 * 如果文件不存在（filePointer为NULL），则尝试以二进制写读模式("wb+")创建并打开文件
 * 如果创建也失败，输出错误提示
 * 最后返回文件指针
 *
 * char *filepath 文件路径字符串指针，指定要打开或创建的文件位置
 */
FILE *FileOpen(char *filepath)
{
    if (filepath == NULL || strlen(filepath) == 0)
    {
        printf("无效的文件路径\n");
        return NULL;
    }

    FILE *filePointer;
    // "rb+"模式：安全地打开现有文件进行读写，不会意外清空已有内容
    // "wb+"模式：在需要时创建新文件，或覆盖现有文件
    filePointer = fopen(filepath, "rb+"); // 文件存在，用r+打开
    if (filePointer == NULL)
    {
        filePointer = fopen(filepath, "wb+"); // 文件不存在，用w+打开
        if (filePointer == NULL)
        {
            // printf("file open failed.\n"); // 打开不成功，作提示
            printf("文件打开失败: %s\n", strerror(errno));
        }
    }
    return filePointer;
}

/**
 * 文件写入
 *
 * FILE *filePointer - 文件指针，指向需要写入数据的文件
 * ListPointer nodeHead - 链表头指针，指向存储待写入数据的链表头节点
 * int size - 单个数据项的字节大小
 * int startPosition - 文件写入的起始位置参数，用于fseek函数的第三个参数
 *
 * 返回值 int - 成功写入到文件中的数据项数量
 */
int FileWrite(FILE *filePointer, ListPointer nodeHead, int size, int startPosition)
{
    if (filePointer == NULL || nodeHead == NULL || size <= 0)
    {
        return 0; // 参数无效时返回0表示写入失败
    }

    int count = 0;
    ListPointer linkedListNode;

    // 将文件指针移动到指定的起始位置
    fseek(filePointer, 0, startPosition);

    linkedListNode = nodeHead->listNext;

    int flushInterval = 100; // 每写入100条记录刷新一次
    while (linkedListNode != NULL)
    {
        count++; // 每写入一个数据项，计数器count就加1

        // 从n指定的位置写入链表
        if (fwrite(linkedListNode->listData, size, 1, filePointer) != 1)
        {
            // 可以选择中断写入或记录错误
            fprintf(stderr, "写入文件失败\n");
            break; // 或者 continue 继续尝试写入下一项
        }

        // 确保数据被立即写入到磁盘文件
        if (count % flushInterval == 0)
        {
            fflush(filePointer);
        }
        linkedListNode = linkedListNode->listNext;
    }
    fflush(filePointer); // 最后确保全部数据都写入

    return count;
}

/**
 * 从指定文件中读取数据，并将读取的数据存储到链表中返回
 *
 * FILE *filePointer：文件指针，指向要读取数据的文件
 * int size：每次读取的数据块大小（字节数），决定了单个数据项的大小
 *
 * 返回值 ListPointer：返回一个链表头指针，链表中包含了从文件读取的所有数据项
 */
ListPointer FileRead(FILE *filePointer, int size)
{
    if (filePointer == NULL || size <= 0)
    {
        printf("无效的文件指针或数据大小\n");
        return NULL;
    }

    ListPointer nodeHead; // 链表头指针
    int flag;             // 读取成功标识
    void *tempPointer;    // 临时数据指针

    fseek(filePointer, 0, SEEK_SET); // 定位到文件头

    // 初始化一个空链表作为数据存储结构
    nodeHead = LinkedListInit(NULL);
    // 为临时数据指针 tempPointer 分配 size 字节的内存空间
    tempPointer = malloc(size);
    // 使用 fread 函数从文件中读取 size 字节的数据到 tempPointer 指向的内存，
    // 首次读取，并将读取结果保存到 flag（成功读取则 flag=1，否则 flag=0）
    flag = fread(tempPointer, size, 1, filePointer);
    // 成功读取了数据进行链表数据处理
    while (flag)
    {
        // 将读取的数据添加到链表中
        LinkedListAdd(nodeHead, tempPointer);
        // 为下一次读取分配新的内存空间
        tempPointer = malloc(size);
        // 重复读取，继续从文件读取下一个数据块
        flag = fread(tempPointer, size, 1, filePointer);
    }

    // 释放内存
    if (tempPointer)
    {
        free(tempPointer);
        tempPointer = NULL;
    }

    return nodeHead; // 返回读出链表的头指针
}

/**
 * 文件更新
 *
 * FILE *filePointer：文件指针，指向要更新数据的文件
 * ListPointer nodeHead：链表头指针，包含了从文件读取的数据
 * int size：每个数据项的大小（字节数）
 * int (*pointerUpdate)(ListPointer)：函数指针，用于自定义查找和更新逻辑，接收链表节点作为参数，返回非零值表示找到匹配的数据项
 */
void FileUpdate(FILE *filePointer, ListPointer nodeHead, int size, int (*pointerUpdate)(ListPointer))
{
    int count = 0;
    ListPointer linkedListNode;
    linkedListNode = nodeHead->listNext;
    while (linkedListNode != NULL)
    {
        count++; // 记录要更新的数据在链表中的位置
        // 调用用户提供的匹配函数 pointerUpdate(linkedListNode) 检查当前节点是否为目标数据
        if (pointerUpdate(linkedListNode)) // 查找要更新的数据
        {
            break;
        }
        linkedListNode = linkedListNode->listNext;
    }
    if (linkedListNode == NULL)
    {
        printf("\n\t\t!!! 未查到此条信息 !!!");
    }
    else
    {
        printf("\n\t\t### 是否确认修改(y)是、(n)不:");
        if (FormatInputString(NULL, 0, 0, 1, 1) == 'y')
        {
            fseek(filePointer, (count - 1) * size, SEEK_SET);       // 定位到要更新的结点位置
            fwrite(linkedListNode->listData, size, 1, filePointer); // 覆盖旧数据
            fflush(filePointer);
            printf("\n\t\t!!! 修改成功 !!!");
        }
        else
        {
            printf("\n\t\t!!! 修改失败 !!!");
        }
    }
    printf("，按任意键继续...");
    getch();
}

// 假删除，更改数据状态
// 通过 while 循环查询到待操作的结点数据并记录结点的位置，接着通过
// if…else 语句对定位到的结点数据进行覆盖操作，这样旧的数据就被移除了
//
// TODO 方法名修改为 FileLogicalDelete
void FileRemove(FILE *filePointer, ListPointer nodeHead, int size, int (*pointerRemove)(ListPointer))
{
    ListPointer linkedListNode;
    int count;
    count = 0;
    linkedListNode = nodeHead->listNext;
    while (linkedListNode != NULL)
    {
        count++;                           // 记录结点的位置
        if (pointerRemove(linkedListNode)) // 查询要操作的结点数据
            break;
        linkedListNode = linkedListNode->listNext;
    }
    if (linkedListNode == NULL)
    {
        printf("\n\t\t!!! 未查到此条信息 !!!");
    }
    else
    {
        printf("\n\t\t### 是否确认删除(y)是、(n)不:");
        if (FormatInputString(NULL, 0, 0, 1, 1) == 'y') // 提示是否确定删除
        {
            fseek(filePointer, (count - 1) * size, SEEK_SET);       // 定位到要假删除的结点位置
            fwrite(linkedListNode->listData, size, 1, filePointer); // 覆盖旧数据
            fflush(filePointer);
            printf("\n\t\t!!! 删除成功 !!!");
        }
        else
        {
            printf("\n\t\t!!! 删除失败 !!!");
        }
    }
    printf("，按任意键继续...");
    getch();
}

// 真删除
void FileDelete(FILE *filePointer, char dir[], ListPointer nodeHead,
                int size, int (*pointerDelete)(ListPointer))
{
    ListPointer linkedListNode;
    ListPointer tempNodePointer;
    tempNodePointer = nodeHead;
    linkedListNode = nodeHead->listNext;
    while (linkedListNode != NULL)
    {
        if (pointerDelete(linkedListNode))
        {
            printf("\n\t\t### 是否确认删除(y)是、(n)不:");
            if (FormatInputString(NULL, 0, 0, 1, 1) == 'y')
            {
                tempNodePointer->listNext = linkedListNode->listNext;
                free(linkedListNode); // 释放要删除的结点
                fclose(filePointer);
                filePointer = fopen(dir, "w+");            // 已W+方式重新打开文件
                FileWrite(filePointer, nodeHead, size, 0); // 重新把链表写入文件
                printf("\n\t\t!!! 删除成功 !!!");
            }
            else
            {
                printf("\n\t\t!!! 删除失败 !!!");
            }
            break;
        }
        tempNodePointer = linkedListNode;
        linkedListNode = linkedListNode->listNext;
    }
    if (linkedListNode == NULL)
    {
        printf("\n\t\t!!! 未找到此条信息 !!!");
    }
    printf("，按任意键继续...");
    getch();
}

// 文件数据添加
void FileAdd(FILE *filePointer, ListPointer nodeHead, int size, void *(*pointerAdd)(void *data))
{
    ListPointer newListPointer;
    ListPointer linkedListNode;
    linkedListNode = nodeHead;
    while (linkedListNode->listNext != NULL)
    {
        linkedListNode = linkedListNode->listNext;
    }
    newListPointer = LinkedListInit(pointerAdd(linkedListNode->listData));
    printf("\t\t### 是否确认增加(y)是、(n)不:");
    if (FormatInputString(NULL, 0, 0, 1, 1) == 'y') // 提示是否确定增加
    {
        linkedListNode->listNext = newListPointer;
        fseek(filePointer, 0, SEEK_END);                        // 定位到文件末尾添加数据
        fwrite(newListPointer->listData, size, 1, filePointer); // 加入新增的数据
        fflush(filePointer);
        printf("\n\t\t!!! 增加成功 !!!");
    }
    else
    {
        printf("\n\t\t!!! 增加失败 !!!");
    }
    printf("，按任意键继续...");
    getch();
}